package com.foreveross.proxyip.core.pools.validate;

import java.util.Date;

import javax.net.SocketFactory;

import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.CoreConnectionPNames;
import org.apache.http.params.HttpParams;
import org.apache.http.util.EntityUtils;

import com.dayatang.domain.InstanceFactory;
import com.foreveross.proxyip.core.BaseCode;
import com.foreveross.proxyip.core.http.ProxyIpHttpClientRequest;
import com.foreveross.proxyip.core.pools.ChannelPool;
import com.foreveross.proxyip.core.pools.PoolsManager;
import com.foreveross.proxyip.core.pools.PublicPool;
import com.foreveross.proxyip.core.redis.PiRedisExecption;
import com.foreveross.proxyip.entity.ProxyIpBean;

public abstract class TestProxyIp extends ProxyIpHttpClientRequest {

	public boolean isPing = true;

	protected int connectTimeout, soTimeout, maxUsedCount;
	protected long ipAvailTime;
	protected Integer valiSeparTime;
	
	protected int channelCount = 0;//渠道池数量（包括默认池）

	protected Integer proxyValiType = null;

//	private Long channelIpOutSepTime = null;// 渠道池ip检出的时间需要的间隔时间

	protected String poolSign = null;// 池标记
	
	public Integer[] speedGrades = null;

	protected static SocketFactory _socketFactory_;
	
	private static Boolean isTestipLog = false;
	
	
	PoolsManager poolsManager;
	
	protected void setSpeedGrades(String speedGrade){
		if(StringUtils.isBlank(speedGrade)){
			return ;
		}else{
			String[] gradeStrs = StringUtils.split(speedGrade,":");
			speedGrades = new Integer[gradeStrs.length];
			for(int i=0;i<gradeStrs.length;i++){
				String gradeStr = gradeStrs[i];
				speedGrades[i] = Integer.parseInt(gradeStr);
			}
		}
	}

	public TestProxyIp(String poolSign) {
		poolsManager = InstanceFactory
		.getInstance(PoolsManager.class);
		this.poolSign = poolSign;
		try {
			String isLog = BaseCode.getValue("TESTIP_LOG");
			isTestipLog = isLog.equals("1");
		} catch (PiRedisExecption e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		
	}

	public abstract void setConfig() throws NumberFormatException, PiRedisExecption;

	/**
	 * 
	 * @param proxyIp
	 * @param type
	 *            检入：1；检出：2
	 * @return 0、失败；1、成功；2、未检测
	 */
	public int validate(ProxyIpBean proxyIp, Integer type) {
		boolean statu = false;
		try {
			if(proxyIp==null){
				return 0;
			}
			Long currTime = System.currentTimeMillis();
			/*if(this instanceof ChannelTestProxyIp&&type==1){
				log.debug(this.poolSign + "validate begin.......................");
				log.debug(this.poolSign + "this.checkUseCount(proxyIp, type):"+this.checkUseCount(proxyIp, type));
				log.debug(this.poolSign + "this.checkIpAvaiTime(proxyIp, currTime, type):"+this.checkIpAvaiTime(proxyIp, currTime, type));
				log.debug(this.poolSign + "this.checkOutTime(proxyIp, currTime, type):"+this.checkOutTime(proxyIp, currTime, type));
				log.debug(this.poolSign + "this.checkChannel(proxyIp, type):"+this.checkChannel(proxyIp, type));
				log.debug(this.poolSign + "this.checkPulicUsing(proxyIp, type):"+this.checkPulicUsing(proxyIp, type));
				
			}*/
			proxyIp.setRetrieveTime(new Date());
			
			if (this.checkUseCount(proxyIp, type)
					|| this.checkSpeed(proxyIp)
					|| this.checkIpAvaiTime(proxyIp, currTime, type)
					|| this.checkOutTime(proxyIp, currTime, type)
					|| this.checkChannel(proxyIp, type)
					||this.checkPulicUsing(proxyIp, type)) {
				
				return 0;
			}

			if (checkVliTime(proxyIp, currTime)) {
				return 2;
			}
//			checkLastCheckinTime
			if (isValidate()) {
				if(type == 1){//checekIn
					if((this instanceof PublicTestProxyIp) || !this.checkLastCheckinTime(proxyIp, currTime, type)){
 						statu = this.connect(proxyIp,type);
					}else{
						statu = this.pingConnect(proxyIp);
					}
				}else{
					statu = this.pingConnect(proxyIp);
				}
				
			} else {
				return 1;
			}
			
 			long endtime = System.currentTimeMillis();
			int responseTime = Integer.parseInt((endtime - currTime) + "");// 记录响应时间
			proxyIp.setResponseTime(responseTime);
		} catch (Exception e) {
			
			this.setRefuse(proxyIp);
			// log.error("--------代理ip："+proxyIp.getIp()+"连接异常--------");
			return 0;
		}
		
		if (statu ) {
			// log.info("--------代理ip："+proxyIp.getIp()+"正常服务--------");
			return 1;
		} else {
			this.setRefuse(proxyIp);
			// log.error("--------代理ip：状态："+statu+"："+proxyIp.getIp()+"不能正常服务--------");
			return 0;
		}
	}

	/**
	 * 判断上次验证是否还有效
	 * 
	 * @param proxyIp
	 * @param currTime
	 * @return
	 */
	private boolean checkVliTime(ProxyIpBean proxyIp, Long currTime) {
		Long preTime = proxyIp.getChannelVliTime().get(poolSign);
		
		if (preTime != null) {
			Long separation = currTime - preTime;
			if (separation < valiSeparTime) {// 上次验证仍然有效
				logInfo("checkVliTime :::"+poolSign);
				return true;
			}
		}
		// 设置该代理ip在池中当前验证时间
		proxyIp.getChannelVliTime().put(poolSign, currTime);
		
		return false;
	}
	/**
	 * 检查ip是否被渠道池（除默认池）全部拒绝
	 * 
	 * @param proxyIp
	 * @param type
	 * @return
	 */
	private boolean checkChannel(ProxyIpBean proxyIp, Integer type){
		if(type==2 && this instanceof PublicTestProxyIp){
			channelCount = poolsManager.getChannelPoolMap().size();
			if(channelCount==0){
				return false;
			}
			
			if(proxyIp.getRefuseChannel().contains(ChannelPool.DEFAULT_CODE)){
				return proxyIp.getRefuseChannel().size()>=channelCount;
			}else{
				boolean bool = proxyIp.getRefuseChannel().size()/2>=channelCount-1;
				if(bool){
					logInfo("checkChannel :::"+poolSign);
				}
			}
		}
		return false;
	}
	/**
	 * 如果是渠道池检出验证，且ip不存在于公共池，那么删除
	 * @param proxyIp
	 * @param type
	 * @return
	 */
	private boolean checkPulicUsing(ProxyIpBean proxyIp, Integer type){
		if(type==2&&this instanceof ChannelTestProxyIp){
			if(proxyIp.getUsingKeys().contains(PublicPool.POOL_CODE)){
				return false;
			}else{
				logInfo("checkPulicUsing :::"+poolSign);
				return true;
			}
		}
		return false;
	}

	/**
	 * 检测ip池中使用次数 渠道池检入和公共池检出检验ip使用次数
	 * 
	 * @return
	 */
	private boolean checkUseCount(ProxyIpBean proxyIp, Integer type) {
		if ((type == 1 && this instanceof ChannelTestProxyIp)
				|| (type == 2 && this instanceof PublicTestProxyIp)) {
			Integer count = proxyIp.getChannelUseCount().get(poolSign);
			if (count == null || count == 0) {// first validate
				return false;
			}
			boolean bool = count >= maxUsedCount;
			if(bool){
				logInfo("checkUseCount :::"+poolSign);
			}
			return bool;
		} else {
			return false;
		}

	}

	/**
	 * 渠道池检入的时候验证该ip上次检出的时间，只有大于指定的时间才能再次放入
	 * 
	 * @param proxyIp
	 * @param currTime
	 * @param type
	 * @return
	 * @throws PiRedisExecption 
	 * @throws NumberFormatException 
	 */
	private boolean checkOutTime(ProxyIpBean proxyIp, Long currTime,
			Integer type) throws NumberFormatException, PiRedisExecption {
		if (type == 1 && this instanceof ChannelTestProxyIp) {
			ChannelPool pool = poolsManager.getChannelPoolMap().get(poolSign);
			
			if(pool == null){//非渠道池跳过
				return false;
			}else{
				//上次检出是否未过期
				boolean bool = pool.redisManager.contain(pool.getIpOutTimeKey(proxyIp));
				
				if(bool){
					logInfo("checkOutTime :::" + poolSign);
				}
				
				return bool;
//				if (outTime == null || outTime == 0L) {// first validate
//					return false;
//				}
//				return true;
			}
			
//			if (channelIpOutSepTime == null) {
//				channelIpOutSepTime = Long.parseLong(BaseCode.getValue("CHANNEL_IP_OUT_SEP_TIME"));
//			}
//			return currTime - outTime < channelIpOutSepTime;
		} else {//检出跳过
			return false;
		}
	}
	
	/**
	 * ip是否还在最后一次检查的有效期内，如果在就无需再次目标测试
	 * 
	 * @param proxyIp
	 * @param currTime
	 * @param type
	 * @return
	 * @throws NumberFormatException
	 * @throws PiRedisExecption
	 */
	private boolean checkLastCheckinTime(ProxyIpBean proxyIp, Long currTime,
			Integer type) throws NumberFormatException, PiRedisExecption {
		if (type == 1 && this instanceof ChannelTestProxyIp) {
			ChannelPool pool = poolsManager.getChannelPoolMap().get(poolSign);
			
			if(pool==null){//非渠道池跳过
				return false;
			}else{
				//上次检出是否未过期
				
				boolean bool = pool.redisManager.contain(pool.getIpLastCheckinTimeKey(proxyIp));
				
				if(bool){
					logInfo("checkLastCheckinTime :::"+poolSign);
				}
				
				return bool;
//				if (outTime == null || outTime == 0L) {// first validate
//					return false;
//				}
//				return true;
			}
			
//			if (channelIpOutSepTime == null) {
//				channelIpOutSepTime = Long.parseLong(BaseCode.getValue("CHANNEL_IP_OUT_SEP_TIME"));
//			}
//			return currTime - outTime < channelIpOutSepTime;
		} else {//检出跳过
			return false;
		}
	}

	/**
	 * 检出的时候验证ip是否超过有效时间
	 * 
	 * @param proxyIp
	 * @return
	 */
	private boolean checkIpAvaiTime(ProxyIpBean proxyIp, Long currTime,
			Integer type) {
		if (type == 2 && this instanceof ChannelTestProxyIp && !isValidate()) {
			Long time = proxyIp.getIp2ChannelTime().get(poolSign);
			if (time == null || time == 0l) {
				
				return false;
			}
			
			boolean bool = (currTime - time) >= this.ipAvailTime;
			
			if(bool){
				logInfo("checkIpAvaiTime :::"+poolSign);
			}
			
			return bool;
		} else {
			return false;
		}
	}

	public HttpParams getHttpParams() {
		HttpParams httpParams = new BasicHttpParams();
		httpParams.setParameter(CoreConnectionPNames.CONNECTION_TIMEOUT,
				connectTimeout);
		httpParams.setParameter(CoreConnectionPNames.SO_TIMEOUT, soTimeout);
		return httpParams;
	}

	/**
	 * 是否验证
	 * 
	 * @return
	 */
	public abstract boolean isValidate();

	/**
	 * 代理连接url验证需要实现该方法，获取配置中的一个池验证url
	 * 
	 * @return
	 */
	protected abstract String getConnectUrl();

	/**
	 * 钩子，设置连接不上的渠道，channel需要重写
	 * 
	 * @param proxyIp
	 */
	public void setRefuse(ProxyIpBean proxyIp) {
	}
	
	/**
	 * 钩子 代理检测网速是否买足渠道
	 * @return
	 */
	public boolean checkSpeed(ProxyIpBean proxyIp){
		return false;
	}
/**
 * 代理ip连接一次url
 */
	public boolean proxyConnect(ProxyIpBean proxyIp, String url,
			HttpParams httpParams) throws Exception {
		HttpEntity entity = null;
		HttpGet httpGet = null;
		Integer contentLent = 0;
		String statu = "false";
		Long startTime = System.currentTimeMillis();
		try{
			HttpClient httpclient = this.getHttpClient(proxyIp, httpParams);
			httpGet = new HttpGet(url);
			HttpResponse response = httpclient.execute(httpGet);
			statu = response.getStatusLine().toString();
			entity = response.getEntity();
			if (statu != null && statu.indexOf("200") != -1) {
//				String html = EntityUtils.toString(entity);
//				log.debug(html);
				statu = "200";
				contentLent = EntityUtils.toByteArray(entity).length;
				EntityUtils.consume(entity);
//				if(StringUtils.isNotBlank(html)&&StringUtils.contains(html, StringUtils.replace(url, "http://", ""))){
//					return true;
//				}
				return true;
			}
			EntityUtils.consume(entity);
			return false;
		}catch(Exception e){
			statu = "false";
 			return false;
		}finally{
			if((proxyValiType==1 || proxyValiType==2) && "200".equals(statu)){
				Long avgTime = (contentLent*100/(System.currentTimeMillis()-startTime))/100;
				int speed = getSpeedGrade(avgTime.intValue());
				if(speed==0){
					return false;
				}
				proxyIp.setSpeedGrade(speed);
			}
			
		}
	}
	
	public Integer getSpeedGrade(Integer speed ){
		if(speed<speedGrades[0]){//网速太烂，无效
			return 0;
		}else if(speed>=speedGrades[speedGrades.length-1]){//超级ip
			return speedGrades.length;
		}
		for(int i = 1;i<speedGrades.length;i++){//ip三六九等
			if(speedGrades[i-1]<=speed&&speed<speedGrades[i]){
				return i;
			}
		}
		return 0;
	}

	public boolean pingConnect(ProxyIpBean proxyIp)throws Exception {
			return super.pingConnect(proxyIp.getIp(), proxyIp.getPort(), connectTimeout);
	}

	

	public boolean connect(ProxyIpBean proxyIp,Integer type)
			throws Exception {

		if (proxyValiType == null) {
			return false;
		}
		
		if (proxyValiType == 0) {
			return true;
		} else if (proxyValiType == 3) {
			boolean bool = pingConnect(proxyIp);
			if(bool){
//				log.info("pool"+this.poolSign+"  ip:"+proxyIp.getIp()+" is OK!");
			}
			return bool;
		} else if (proxyValiType == 1 || proxyValiType == 2) {
			String url = this.getConnectUrl();
			if(StringUtils.isNotBlank(url)){
				HttpParams httpParams = getHttpParams();
				return proxyConnect(proxyIp, url, httpParams);
			}else{
				return true;
			}
		}
		return false;
	}

	
	public void logInfo(String msg){
		if(isTestipLog){
			log.info(msg);
		}
	}
}
